'use strict'

const EventEmitter3 = require('eventemitter3');

const logger = require('./logger')('Signaling');
const websocket = require('./websocket');


/**
 * Signaling (信令服务器) webscoket
 *    事件:
 *        'ready': 私有/公共频道都订阅成功后触发
 *        'message': 有消息达到时触发, 附加参数 'message'
 *        'warn': 出现警告时触发, 附加参数 'error'
 *        'error': 出现错误时触发,附加参数 'error'
 *        'close': 私有/公共频道都成功关闭后触发
 */
class Signaling extends EventEmitter3 {
  constructor(ws) {
    super();
    if (!(ws instanceof websocket.Ws)) {
      ws = websocket(ws);
    }
    this._ws = ws;
    this._peerId = ws.peerId;
    this.channelPrefix = this._ws.channelPrefix;

    this._webBtChannelName = this.channelPrefix + ':webbt';
    this._webBtChannel = null;
    this._isWebBtChannelSubscribe = false;
    this._personalChannelName = this.channelPrefix + ':' + this._peerId;
    this._personalChannel = null;
    this._isPersonalChannelSubscribe = false;

    this._peerChannels = [];

    this._onLinePeers = {};
    this.isReady = false;
  }

  _emptyPeers() {
    this._onLinePeers = {};
  }

  _peerJoin(peerId, peerInfo) {
    /*
     * peerInfo = {
     *    client: "client"
     * }
     */
    this._onLinePeers[peerId] = peerInfo;
  }

  _peerLeave(peerId) {
    delete this._onLinePeers[peerId];
  }

  _updateOnlinePeers() {
    return new Promise((resolve, reject) => {
      this._webBtChannel.presence().then(context => {
        this._emptyPeers();
        for (let client in context.data) {
          const peerId = context.data[client].user;
          const defaultInfo = context.data[client].default_info;
          this._peerJoin(peerId, { client: client });
        }
        resolve(this._onLinePeers);
      }).catch(error => {
        reject(error);
      });
    });
  }

  _getPeerPersonalChannelName(peerId) {
    return this.channelPrefix + ':' + peerId;
  }

  getPeerInfo(peerId) {
    return this._onLinePeers[peerId];
  }

  getOnLinePeers() {
    return this._onLinePeers;
  }

  connet() {
    const handels = {
      'message':  message => {
        ` 格式:
          "message" = {
            "channel": "download_media:browser-050f96ac4ede4adfbce578ff2c5dd928",
            "uid": 7,       // 序列号
            "data": {
              "peerId": "browser-1cc61d59e24242f3858e684a1a3fb063",   // 发送端peerId
              "type": "CONNECT_OFFER",                                // 消息类型
              "payload": {                                            // 负载
                "connectionId": "xxxxxxxxx",
                "sdp": "xxxxxxxx"
              }
            }
          }
        `
        const channel = message.channel
          , uid = message.uid
          , data = message.data ? message.data : {}
          , peerId = data.peerId
          , type = data.type
          , payload = data.payload ? data.payload : {};

        if (!peerId || !type) {
          logger.debug(`recive a message without peerid/type, ignore.`, message);
          return;
        } else {
          // logger.debug(`recive ${type} message from ${peerId}.`);
        }
        this.emit('message', message.data);
      },
      'join': context => {
        const channel = context.channel
          , peerId = context.data.user
          , client = context.data.client
          , defaultInfo = context.data.default_info;

        if (channel !== this._webBtChannelName) {
          return;
        }
        if (peerId === this._peerId) {
          return;
        }
        // logger.debug(`${peerId} join ${channel}.`);
        this._peerJoin(peerId, { client: client });
        // this.emit('join', {peerId: peerId, channel: channel});
      },
      'leave': context => {
        const channel = context.channel
          , peerId = context.data.user
          , client = context.data.client
          , defaultInfo = context.data.default_info;

        if (channel !== this._webBtChannelName) {
          return;
        }
        if (peerId === this._peerId) {
          return;
        }
        // logger.debug(`${peerId} leave ${channel}.`);
        this._peerLeave(peerId);
        // this.emit('leave', {peerId: peerId, channel: channel});
      },
      'subscribe': context => {
        const channel = context.channel
          , isResubscribe = context.isResubscribe
          , recovered = context.recovered;

        logger.info(`${isResubscribe ? 'resubscribe' : 'subscribe'} ${channel} success.`);
        if (channel === this._webBtChannelName) {
          this._updateOnlinePeers();
          this._isWebBtChannelSubscribe = true;
        } else if (channel === this._personalChannelName) {
          this._isPersonalChannelSubscribe = true;
        }
        if (!this.isReady && this._isWebBtChannelSubscribe && this._isPersonalChannelSubscribe) {
          this.isReady = true;
          this.emit('ready');
        }
      },
      'unsubscribe': context => {
        const channel = context.channel;
        if (channel === this._webBtChannelName) {
          this._isWebBtChannelSubscribe = false;
          this._emptyPeers();
        } else if (channel === this._personalChannelName) {
          this._isPersonalChannelSubscribe = false;
        }
        if (this.isReady) {
          const warnStr = `lost subscribe ${channel}, auto try subscribe later.`;
          logger.warn(warnStr);
          // 不需要从新订阅处理. 会自动重新订阅
          this.emit('warn', new Error(warnStr));
        } else {
          logger.info(`unsubscribe ${channel}`);
          if ((!this._isWebBtChannelSubscribe) && (!this._isPersonalChannelSubscribe)) {
            this.emit('close');
          }
        }
      },
      'error': error => {
        logger.error(`error: `, error);
        this.emit('error', error);
      }
    }
    this._webBtChannel = this._ws.subscribe(this._webBtChannelName, handels);
    this._personalChannel = this._ws.subscribe(this._personalChannelName, handels);
  }

  sendMessage(peerId, data) {
    // FIXME: 每次发送前都需要订阅再发送私密消息, 建议以后使用私有通道发送私密消息(该功能需要服务器转发)
    return new Promise((resolve, reject) => {
      if (!this.isReady) {
        const errStr = 'websocket is not open, connet() first.';
        logger.warn(errStr);
        reject(new Error(errStr));
        return;
      }
      if (!this._isWebBtChannelSubscribe) {
        const errStr = 'channel is unsubscribe, try it latter again.';
        logger.warn(errStr);
        reject(new Error(errStr));
        return;
      }
      const peerInfo = this.getPeerInfo(peerId);
      if (!peerInfo) {
        const errStr = `${peerId} is not online.`;
        logger.warn(errStr);
        reject(new Error(errStr));
        return;
      }

      let targetChannel = null;
      const now = Date.now();
      for (let i in this._peerChannels) {
        const peerChannel = this._peerChannels[i];
        if (peerChannel.expire < now || !peerChannel.channel._ready) {
          peerChannel.channel.removeAllListeners();
          peerChannel.channel.unsubscribe();
          this._peerChannels.splice(i, 1);
        }
        else if (peerChannel.peerId === peerId) {
          peerChannel.expire = now + (60 * 1000);
          targetChannel = peerChannel.channel;
        }
      }
      if (!targetChannel) {
        const targetChannelName = this._getPeerPersonalChannelName(peerId);
        targetChannel = this._ws.subscribe(targetChannelName);
        this._peerChannels.push({
          peerId: peerId,
          expire: now + (60 * 1000),
          channel: targetChannel
        });
      }
      targetChannel.publish(data).then(data => {
        // logger.info(`>>>>>>>>>>>  send messsage to ${peerId} data: `, data, 'is success.');
        resolve(data);
      }, error => {
        // logger.info(`----------> send message to ${peerId} data: `, data, 'is fail!');
        logger.warn(error);
        reject(error);
      });
    });
  }

  broadcast(data) {
    return new Promise((resolve, reject) => {
      if (!this._isWebBtChannelSubscribe) {
        const errStr = 'channel is unsubscribe, try it latter again.';
        logger.warn(errStr);
        reject(new Error(errStr));
        return;
      }
      this._webBtChannel.publish(data).then(data => {
        resolve(data);
      }, error => {
        logger.warn(error);
        reject(error);
      });
    });
  }

  close() {
    this.isReady = false;
    this._webBtChannel.unsubscribe();
    this._personalChannel.unsubscribe();
  }
}

module.exports.Signaling = Signaling;
